﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json;

namespace Alvin.Common.Utils
{
    /// <summary>
    /// 讲一个对象序列化为一个Json字符串，或者将一个Json字符串序列化为一个对象
    /// </summary>
    public static class JsonUtil
    {
        /// <summary>
        /// 长日期
        ///  format:yyyy-MM-dd hh:mm:ss
        /// </summary>
        public const String FULL_DATE_TIME = "yyyy'-'MM'-'dd' 'HH':'mm':'ss";

        /// <summary>
        /// 短日期
        /// format:yyyy-MM-dd
        /// </summary>
        public const String FULL_DATE = "yyyy'-'MM'-'dd'";

        /// <summary>
        /// 短时间
        /// format:hh:mm:ss
        /// </summary>
        public const String FULL_TIME = "'HH':'mm':'ss";

        /// <summary>
        /// 将一个对象序列成Json字符串
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="dateTimeConverter"></param>
        /// <returns>时间格式默认的为长时间</returns>
        public static String Serialize(Object obj, String dateTimeConverter = FULL_DATE_TIME)
        {
            return JsonConvert.SerializeObject(obj, Formatting.Indented, new IsoDateTimeConverter() { DateTimeFormat = dateTimeConverter });
        }

        /// <summary>
        /// 将一个字符串反序列化为泛型类型指定的类的对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="s"></param>
        /// <param name="dateTimeConverter"></param>
        /// <returns></returns>
        public static T Deserialize<T>(String s, String dateTimeConverter = FULL_DATE_TIME) where T : class
        {
            return JsonConvert.DeserializeObject<T>(s, new IsoDateTimeConverter() { DateTimeFormat = dateTimeConverter });
        }

        /// <summary>
        /// 将一个字符串反序列化为指定类型的类的对象
        /// </summary>
        /// <param name="s"></param>
        /// <param name="targetType"></param>
        /// <param name="dateTimeConverter"></param>
        /// <returns></returns>
        public static Object Deserialize(String s, Type targetType, String dateTimeConverter = FULL_DATE_TIME)
        {
            return JsonConvert.DeserializeObject(s, targetType, new IsoDateTimeConverter() { DateTimeFormat = dateTimeConverter });
        }

        public static String NullEmptyString(string s)
        {
            return String.IsNullOrWhiteSpace(s) ? null : s;
        }

        public static string FormatWith(this string format, IFormatProvider provider, params object[] args)
        {
            return String.Format(provider, format, args);
        }

        public static Type GetObjectType(object o)
        {
            return o == null ? null : o.GetType();
        }
    }

    /// <summary>
    /// Converts a <see cref="DateTime"/> to and from the ISO 8601 date format (e.g. 2008-04-12T12:53Z).
    /// </summary>
    public class IsoDateTimeConverter : Newtonsoft.Json.Converters.DateTimeConverterBase
    {
        /// <summary>
        /// "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";
        /// </summary>
        private const string DefaultDateTimeFormat = "yyyy-MM-ddTHH:mm:ss.FFFFFFFK";

        /// <summary>
        /// "yyyy'-'MM'-'dd' 'HH':'mm':'ss";
        /// </summary>
        private const string NormalDateTimeFormat = "yyyy-MM-dd HH:mm:ss";

        /// <summary>
        /// "yyyy'-'MM'-'dd'";
        /// </summary>
        private const string YearDateTimeFormat = "yyyy-MM-dd";

        /// <summary>
        /// "'HH':'mm':'ss";
        /// </summary>
        private const string NormalTimeFormat = "HH:mm:ss";

        /// <summary>
        /// "'HH':'mm':'ss.FFFFFFFK";
        /// </summary>
        private const string DefaultTimeFormat = "HH:mm:ss.FFFFFFFK";

        /// <summary>
        /// yyyyMMdd
        /// </summary>
        private const string EightDateFormat = "yyyyMMdd";

        /// <summary>
        /// yyyyMMddHHmmss
        /// </summary>
        private const string FourteenDateTimeFormat = "yyyyMMddHHmmss";

        private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind;
        private string _dateTimeFormat;
        private CultureInfo _culture;

        /// <summary>
        /// Gets or sets the date time styles used when converting a date to and from JSON.
        /// </summary>
        /// <value>The date time styles used when converting a date to and from JSON.</value>
        public DateTimeStyles DateTimeStyles
        {
            get { return _dateTimeStyles; }
            set { _dateTimeStyles = value; }
        }

        /// <summary>
        /// Gets or sets the date time format used when converting a date to and from JSON.
        /// </summary>
        /// <value>The date time format used when converting a date to and from JSON.</value>
        public string DateTimeFormat
        {
            get { return _dateTimeFormat ?? string.Empty; }
            set { _dateTimeFormat = JsonUtil.NullEmptyString(value); }
        }

        /// <summary>
        /// Gets or sets the culture used when converting a date to and from JSON.
        /// </summary>
        /// <value>The culture used when converting a date to and from JSON.</value>
        public CultureInfo Culture
        {
            get { return _culture ?? CultureInfo.CurrentCulture; }
            set { _culture = value; }
        }

        /// <summary>
        /// Writes the JSON representation of the object.
        /// </summary>
        /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="value">The value.</param>
        /// <param name="serializer">The calling serializer.</param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            string text;

            if (value is DateTime)
            {
                DateTime dateTime = (DateTime)value;

                if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal
                  || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
                    dateTime = dateTime.ToUniversalTime();

                text = dateTime.ToString(_dateTimeFormat ?? DefaultDateTimeFormat, Culture);
            }
            else if (value is DateTimeOffset)
            {
                DateTimeOffset dateTimeOffset = (DateTimeOffset)value;
                if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal
                  || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
                    dateTimeOffset = dateTimeOffset.ToUniversalTime();

                text = dateTimeOffset.ToString(_dateTimeFormat ?? DefaultDateTimeFormat, Culture);
            }
            else
            {
                throw new Exception("Unexpected value when converting date. Expected DateTime or DateTimeOffset, got {0}.".FormatWith(CultureInfo.InvariantCulture, JsonUtil.GetObjectType(value)));
            }

            writer.WriteValue(text);
        }

        /// <summary>
        /// Reads the JSON representation of the object.
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <param name="existingValue">The existing value of object being read.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <returns>The object value.</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            bool nullable = IsNullableType(objectType);
            Type t = (nullable)
              ? Nullable.GetUnderlyingType(objectType)
                  : objectType;

            if (reader.TokenType == JsonToken.Null)
            {
                if (!IsNullableType(objectType))
                    throw new Exception("Cannot convert null value to {0}.".FormatWith(CultureInfo.InvariantCulture, objectType));

                return null;
            }

            if (reader.TokenType != JsonToken.String)
                throw new Exception("Unexpected token parsing date. Expected String, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));

            string dateText = reader.Value.ToString();

            if (string.IsNullOrEmpty(dateText) && nullable)
                return null;

            if (t == typeof(DateTimeOffset))
            {
                if (!string.IsNullOrEmpty(_dateTimeFormat))
                    return DateTimeOffset.ParseExact(dateText, _dateTimeFormat, Culture, _dateTimeStyles);
                else
                    return DateTimeOffset.Parse(dateText, Culture, _dateTimeStyles);
            }

            if (!string.IsNullOrEmpty(_dateTimeFormat))
            {
                DateTime result = DateTime.Now;
                if (!DateTime.TryParseExact(dateText, NormalDateTimeFormat, Culture, _dateTimeStyles, out result) &&
                    !DateTime.TryParseExact(dateText, YearDateTimeFormat, Culture, _dateTimeStyles, out result) &&
                    !DateTime.TryParseExact(dateText, YearDateTimeFormat, Culture, _dateTimeStyles, out result) &&
                    !DateTime.TryParseExact(dateText, DefaultDateTimeFormat, Culture, _dateTimeStyles, out result) &&
                    !DateTime.TryParseExact(dateText, EightDateFormat, Culture, _dateTimeStyles, out result) &&
                    !DateTime.TryParseExact(dateText, FourteenDateTimeFormat, Culture, _dateTimeStyles, out result))
                {
                    throw new Exception("Unexpected token parsing date. Expected String, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
                }
                return result;
            }
            else
                return DateTime.Parse(dateText, Culture, _dateTimeStyles);
        }

        public static bool IsNullableType(Type type)
        {
            return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
        }
    }
}
